#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <elf.h>
#include <link.h>

void print_ehdr(const ElfW(Ehdr) *ehdr);

int main(int argc, char *argv[]) {
    if (argc != 2) {
        // 仅接受一个elf文件的输入
        fprintf(stderr, "使用方法: %s elf文件\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    const char *elf_file_name = argv[1];
    int fd = open(elf_file_name, O_RDONLY);
    if (fd == -1) {
        fprintf(stderr, "打开%s文件失败!\n", elf_file_name);
        exit(EXIT_FAILURE);
    }
    
    // 读取elf文件头
    // ELF文件头主要通过结构体Elf32_Ehdr(32位版)/Elf64_Ehdr(64位版)来定义的，在文件/usr/include/elf.h中.
    // 可以使用"readelf -h elf文件"查看elf文件头信息
    ElfW(Ehdr) ehdr;
    if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) {
        fprintf(stderr, "读取elf文件头失败!");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 校验elf魔数和其他信息，来自e_ident[EI_NIDENT], 比如：Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
    // e_ident[EI_NIDENT] ：
    //  最开始的4个字节是所有ELF文件都相同的标示码，分别为7f、45、4c、46。它们分别对应ascii码表中的delete、E、L、F字符
    //  第5个字节标示ELF文件类型，01表示32位的，02表示64位的
    //  第6个字节标示是大端(big-endian)还是小端(little-endian)
    //  第7个字节表示ELF文件主版本号，之后几个字节无实际意义，默认填0。
    if (ehdr.e_ident[EI_MAG0] != ELFMAG0 ||
        ehdr.e_ident[EI_MAG1] != ELFMAG1 ||
        ehdr.e_ident[EI_MAG2] != ELFMAG2 ||
        ehdr.e_ident[EI_MAG3] != ELFMAG3) {
        fprintf(stderr, "elf文件头魔数校验失败, 因此断定输入的%s文件不是一个有效的elf文件!\n", elf_file_name);
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 打印读取到的elf文件头
    print_ehdr(&ehdr);

    return 0;
}

void print_ehdr(const ElfW(Ehdr) *ehdr)
{
    printf("%s", "ELF文件头:\n");
    // ehdr->e_ident
    // 前四个分别代表 '0x7f'、'E'、'L'、'F'
    printf(" 幻数:    ");
    for (int i=0; i < EI_NIDENT; i++)
	    printf("%02X ", ehdr->e_ident[i]);
    printf("\n");
    switch (ehdr->e_ident[EI_CLASS])
    {
        case ELFCLASSNONE:
            printf(" Class:    %d (%s)\n", ehdr->e_ident[EI_CLASS], "Invalid class");
            break;
        case ELFCLASS32:
            printf(" Class:    %d (%s)\n", ehdr->e_ident[EI_CLASS], "32-bit objects");
            break;
        case ELFCLASS64:
            printf(" Class:    %d (%s)\n", ehdr->e_ident[EI_CLASS], "64-bit objects");
            break;
        default:
            printf(" Class:    %d (%s)\n", ehdr->e_ident[EI_CLASS], "未识别的Class!");
            break;
    }
    switch (ehdr->e_ident[EI_DATA])
    {
        case ELFDATANONE:
            printf(" 数据编码:    %d (%s)\n", ehdr->e_ident[EI_DATA], "Invalid data encoding");
            break;
        case ELFDATA2LSB:
            printf(" 数据编码:    %d (%s)\n", ehdr->e_ident[EI_DATA], "2's complement, little endian");
            break;
        case ELFDATA2MSB:
            printf(" 数据编码:    %d (%s)\n", ehdr->e_ident[EI_DATA], "2's complement, big endian");
            break;
        default:
            break;
    }

    printf(" Version: %d\n", ehdr->e_ident[EI_VERSION]);

    switch (ehdr->e_ident[EI_OSABI])
    {
        case ELFOSABI_SYSV:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "UNIX System V ABI");
            break;
        case ELFOSABI_HPUX:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "HP-UX");
            break;
        case ELFOSABI_NETBSD:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "NetBSD.");
            break;
        case ELFOSABI_LINUX:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "Object uses GNU ELF extensions.");
            break;
        case ELFOSABI_SOLARIS:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "Sun Solaris.");
            break;
        case ELFOSABI_AIX:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "IBM AIX.");
            break;
        case ELFOSABI_IRIX:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "SGI Irix.");
            break;
        case ELFOSABI_FREEBSD:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "FreeBSD.");
            break;
        case ELFOSABI_TRU64:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "Compaq TRU64 UNIX. ");
            break;
        case ELFOSABI_MODESTO:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "Novell Modesto. ");
            break;
        case ELFOSABI_OPENBSD:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "OpenBSD.");
            break;
        case ELFOSABI_ARM_AEABI:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "ARM EABI");
            break;
        case ELFOSABI_ARM:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "ARM");
            break;
        case ELFOSABI_STANDALONE:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "Standalone (embedded) application");
            break;
        default:
            printf(" OS ABI标识:   %d (%s)\n", ehdr->e_ident[EI_OSABI], "未识别的OA ABI标识!");
            break;
    }

    printf(" ABI Version: %d\n", ehdr->e_ident[EI_ABIVERSION]);

    // ehdr->e_type
    switch (ehdr->e_type)
    {
        case ET_NONE:
            printf(" 文件类型:  %s\n", "No file type");
            break;
        case ET_REL:
            printf(" 文件类型:  %s\n", "可重定位文件(Relocatable File)");
            break;
        case ET_EXEC:
            printf(" 文件类型:  %s\n", "可执行文件(Executable File)");
            break;
        case ET_DYN:
            printf(" 文件类型:  %s\n", "共享目标文件(Shared Object File)");
            break;
        case ET_CORE:
            printf(" 文件类型:  %s\n", "核心转储文件(Core Dump File)");
            break;
        default:
            printf(" 文件类型:  %s\n", "未识别到的elf文件类型");
            break;
    }

    // Legal values for e_machine (architecture) from /usr/include/elf.h
    switch (ehdr->e_machine)
    {
        case EM_NONE:
            printf(" 机器架构： %s\n", "No machine");
            break;
        case EM_X86_64:
            printf(" 机器架构： %s\n", "AMD x86-64 architecture");
            break;
        case EM_AARCH64:
            printf(" 机器架构： %s\n", "ARM AARCH64");
            break;
        default:
            // 架构信息太多，有200+，上面仅识别aarch64/x86_64架构
            printf(" 非aarch64/x86_64架构, machine index是%d, 具体架构信息请参考:/usr/include/elf.h\n", ehdr->e_machine);
            break;
    }
    switch (ehdr->e_version)
    {
        case EV_NONE:
            printf(" 文件版本： %d (%s)\n", ehdr->e_version, "Invalid ELF version");
            break;
        case EV_CURRENT:
            printf(" 文件版本： %d (%s)\n", ehdr->e_version, "Current version");
            break;
        default:
            printf(" 文件版本： %d (%s)\n", ehdr->e_version, "Unknown ELF version");
            break;
    }

    printf(" 程序入口的虚拟地址: %p\n", ehdr->e_entry);
}
